import Vector2 from "./Vector2";
import MatrixStack from "./MatrixStack";

export default class Math2D {
    public static EPSILON: number = 0.00001;

    public static matrixStack: MatrixStack = new MatrixStack();
    // 角度转换弧度
    public static toRadian(degree: number): number {
        return degree * Math.PI / 180;
    }

    // 弧度转换角度
    public static toDegree(radian: number): number {
        return radian * 180 / Math.PI;
    }

    public static random(from: number, to: number): number {
        return Math.random() * to + from;
    }

    public static angleSubtract(from: number, to: number): number {
        let diff: number = to - from;
        while (diff > 180) {
            diff -= 360;
        }

        while (diff < - 180) {
            diff += 360;
        }

        return diff;
    }

    public static isEquals(left: number, right: number, espilon: number = Math2D.EPSILON): boolean {
        if (Math.abs(left - right) >= Math2D.EPSILON) {
            return false;
        }
        return true;
    }

    /**
     * *将一个点pt投影到start和end形成的线段上
     * *返回值：true表示pt在线段起点和终点之间，此时closePoint输出参数返回线段上的投影点坐标，false表示在线段起点或终点之外，此时closePoint输出参数返回线段的起点或终点
     * *本方法使用了向量的difference、normalize、dotProduct、scaleAdd（scale和sum方法）
     * @param pt 
     * @param start 
     * @param end 
     * @param closePoint 
     */
    public static projectPointOnLineSegment(pt: Vector2, start: Vector2, end: Vector2, closePoint: Vector2): boolean {
        // 向量的create方法
        let v0: Vector2 = Vector2.create();
        let v1: Vector2 = Vector2.create();
        let d: number = 0;
        // 向量减法，形成方向向量
        Vector2.difference(pt, start, v0);         // 线段的起点到某个点（例如鼠标位置点）的方向向量
        Vector2.difference(end, start, v1);        // 获取线段
        // 使用向量的normalize方法，原向量变成单位向量，并返回原向量的长度
        // 需要注意的是，normalize起点到终点线段形成的向量
        // 要投影到哪个向量，就要将这个向量normalize成单位向量
        d = v1.normalize();
        // 将vo投影在v1上，获取投影长度t
        let t: number = Vector2.dotProduct(v0, v1);
        // 如果投影长度t < 0，说明鼠标位置点在线段的起点范围之外
        // 算是的方式是：
        // closePt输出线段起点并且返回false
        if (t < 0) {
            closePoint.x = start.x;
            closePoint.y = start.y;
            return false;
        } else if (t > d) { // 如果投影长度 > 线段的长度，说明鼠标位置点超过线段终点范围
            // closePt输出线段起点并且返回false
            closePoint.x = end.x;
            closePoint.y = end.y;
            return false;
        } else {
            // 说明鼠标位置点位于线段起点和终点之间
            // 使用scaleAdd方法计算出相对全局坐标(左上角)的坐标偏移信息
            // 只有此时才返回true
            Vector2.scaleAdd(start, v1, t, closePoint);
            return true;
        }
    }

    // 圆由圆心坐标和半径表示
    public static isPointInCircle(pt: Vector2, center: Vector2, radius: number): boolean {
        let diff: Vector2 = Vector2.difference(pt, center);
        let len2: number = diff.squaredLength;
        // 如果一个点在圆的半径范围之内，说明发生了碰撞
        if (len2 <= radius * radius) {
            return true;
        }
        return false;
    }

    /**
     * *点是否在线上
     * @param pt 
     * @param start 
     * @param end 
     * @param radius 
     */
    public static isPointOnLineSegment(pt: Vector2, start: Vector2, end: Vector2, radius: number = 2): boolean {
        let closePt: Vector2 = Vector2.create();
        if (Math2D.projectPointOnLineSegment(pt, start, end, closePt) === false) {
            return false;
        }
        // 需要进行点与圆的碰撞检测
        return Math2D.isPointInCircle(pt, closePt, radius);
    }

    // 点与矩形的碰撞检测
    public static isPointInRect(ptX: number, ptY: number, x: number, y: number, w: number, h: number): boolean {
        // 一个点在矩形的上下左右范围之内，则发生碰撞
        if (ptX >= x && ptX <= x + w && ptY >= y && ptY <= y + h) {
            return true;
        }
        return false;
    }

    // 点与椭圆的碰撞检测
    public static isPointInEllipse(ptX: number, ptY: number, centerX: number, centerY: number, radiusX: number, radiusY: number): boolean {
        let diffX = ptX - centerX;
        let diffY = ptY - centerY;
        let n: number = (diffX * diffX) / (radiusX * radiusX) + (diffY * diffY) / (radiusY * radiusY);
        return n <= 1.0;
    }

    // 计算三角形两条边向量的叉积
    public static sign(v0: Vector2, v1: Vector2, v2: Vector2): number {
        // e1 = v2 -> v0边向量
        let e1: Vector2 = Vector2.difference(v0, v2);
        // e2 = v2 -> v1边向量
        let e2: Vector2 = Vector2.difference(v1, v2);
        // 获取e1和e2的叉积
        return Vector2.crossProduct(e1, e2);
    }

    // 点与三角形的碰撞检测
    public static isPointInTriangle(pt: Vector2, v0: Vector2, v1: Vector2, v2: Vector2) {
        // 计算三角形三个顶点与点pt形成的三个子三角形的边向量的叉积
        let b1: boolean = Math2D.sign(v0, v1, pt) < 0.0;
        let b2: boolean = Math2D.sign(v1, v2, pt) < 0.0;
        let b3: boolean = Math2D.sign(v2, v0, pt) < 0.0;
        // 三角形三条边的方向都一致，说明点在三角形内部，否则就在三角形外部
        return ((b1 === b2) && (b2 === b3));
    }

    // 凸多边形由点的集合表示
    public static isPointInPolygon(pt: Vector2, points: Vector2[]): boolean {
        if (points.length < 3) {
            return false;
        }
        for (let i: number = 2; i < points.length; i++) {
            if (Math2D.isPointInTriangle(pt, points[0], points[i - 1], points[i])) {
                return true;
            }
        }
        return false;
    }

    // 判断是不是凸多边形
    public static isConvex(points: Vector2[]): boolean {
        // 第一个三角形的顶点顺序
        let sign: boolean = Math2D.sign(points[0], points[1], points[2]) < 0;
        // 从第二个三角形开始遍历
        let j: number, k: number;
        for (let i: number = 1; i < points.length; i++) {
            j = (i + 1) % points.length;
            k = (i + 2) % points.length;
            // 如果当前的三角形的顶点方向和第一个三角形的顶点方向不一致，说明是凹多边形
            if (sign !== Math2D.sign(points[i], points[j], points[k]) < 0) {
                return false;
            }
        }
        // 凸多边形
        return true;
    }

    public static getQuadraticBezierVector(start: Vector2, ctrl: Vector2, end: Vector2, t: number, result: Vector2 | null = null): Vector2 {
        if (result === null) result = Vector2.create();
        result.x = Math2D.getQuadraticBezierPosition(start.x, ctrl.x, end.x, t);
        result.y = Math2D.getQuadraticBezierPosition(start.y, ctrl.y, end.y, t);
        return result;
    }

    public static getQuadraticBezierPosition(start: number, ctrl: number, end: number, t: number): number {
        if (t < 0.0 || t > 1.0) {
            alert(" t的取值范围必须为[ 0 , 1 ] ");
            throw new Error(" t的取值范围必须为[ 0 , 1 ] ");
        }
        let t1: number = 1.0 - t;
        let t2: number = t1 * t1;
        return t2 * start + 2.0 * t * t1 * ctrl + t * t * end;
    }

    /* 

    

    public static getQuadraticBezierMat(start: Vector2, ctrl: Vector2, end: Vector2, t: number, result: Vector2 | null = null): Vector2 {
        if (result === null) result = Vector2.create();

        return result;
    }

    public static getCubicBezierPosition(start: number, ctrl0: number, ctrl1: number, end: number, t: number): number {
        if (t < 0.0 || t > 1.0) {
            alert(" t的取值范围必须为[ 0 , 1 ] ");
            throw new Error(" t的取值范围必须为[ 0 , 1 ] ");
        }
        let t1: number = (1.0 - t);
        let t2: number = t * t;
        let t3: number = t2 * t;
        return (t1 * t1 * t1) * start + 3 * t * (t1 * t1) * ctrl0 + (3 * t2 * t1) * ctrl1 + t3 * end;
    }

    public static getCubicBezierVector(start: Vector2, ctrl0: Vector2, ctrl1: Vector2, end: Vector2, t: number, result: Vector2 | null = null): Vector2 {
        if (result === null) result = Vector2.create();
        result.x = Math2D.getCubicBezierPosition(start.x, ctrl0.x, ctrl1.x, end.x, t);
        result.y = Math2D.getCubicBezierPosition(start.y, ctrl0.y, ctrl1.y, end.y, t);
        return result;
    }

    public static createQuadraticBezierEnumerator(start: Vector2, ctrl: Vector2, end: Vector2, steps: number = 30): IBezierEnumerator {
        return new BezierEnumerator(start, end, ctrl, null, steps);
    }

    public static createCubicBezierEnumerator(start: Vector2, ctrl0: Vector2, ctrl1: Vector2, end: Vector2, steps: number = 30): IBezierEnumerator {
        return new BezierEnumerator(start, end, ctrl0, ctrl1, steps);
    }

   

    public static transform(mat: mat2d, pt: Vector2, result: Vector2 | null = null): Vector2 {
        if (result === null) result = Vector2.create();
        result.values[0] = mat.values[0] * pt.values[0] + mat.values[2] * pt.values[1] + mat.values[4];
        result.values[1] = mat.values[1] * pt.values[0] + mat.values[3] * pt.values[1] + mat.values[5];
        return result;
    }

    public static matStack: MatrixStack = new MatrixStack(); */
}